package com.hamishmedlin.d64pw;

import com.hamishmedlin.d64pw.FailedDecode;

public class QDecoder {

	
	private static String unFormatPassword(String pw){
		pw = pw.toLowerCase();
		pw = pw.trim();
		pw = pw.replace(" ", "");
		return pw;
	}
	
	public static boolean validatePasswordBasic(String pw){
		if(pw.length() != 16){			
			System.err.println("Password must contain 16 significant characters! Length given:" + pw.length());
			return false;
		}
		
		for(int i = 0;i<16;i++){
			if(QGlobals.passwordChar.indexOf(pw.charAt(i)) == -1){
				System.err.println("Password contains an invalid character! Valid chars are: " + QGlobals.passwordChar);
				return false;
			}			
		}
		
		if("rvnh3ct1cd3m0???".equals(pw)){
			System.err.println("Congratulations, you unlocked the Hectic demo! But, this password doesn't hash.");
			return false;
		}
		
		for(int i = 0;i < 16;i++){
			QGlobals.passwordData[i] = QGlobals.passwordChar.indexOf(pw.charAt(i));
		}
		
		return true;
	}
	
	
	public static Fields decodePassword(Fields fields,String password) throws FailedDecode{
		
		String pw = password;
		pw = unFormatPassword(pw);
		if(!validatePasswordBasic(pw)){			
			throw new FailedDecode("Password is in an invalid format");
		}
		
		int[] data = new int[16];
		int[] decode = new int[10];
		int bit = 0;
		int xbit1 = 0;
		int xbit2 = 0;
		int xbit3 = 0;
		int x,y;
		int i,j,passBit,decodeBit,checkByte;
		
		
		for(i = 0; i < 16;i++){
			data[i] = QGlobals.passwordData[i];
		}
		
		for(i = 0;i< 10;i++){
			decode[i] = 0;
		}
		
		
		while(bit < 0x50){
			passBit = 0;
			decodeBit = 0x80;
			checkByte = 0;
			i = 0;
			while(i != 8){
				i += 4;
				for(j = 0;j<4;j++){
					checkByte = data[QUtils.intDivide(bit, 5)];
					int temp = checkByte & (16 >> QUtils.intMod(bit, 5));
					if(temp != 0){
						passBit |= decodeBit;
					}
					++bit;
					decodeBit >>= 1;
				}
			}
			
			if((bit -1) >= 0){
				checkByte = (bit - 1 ) >> 3;
			}else{
				checkByte = ((bit-1) + 7) >> 3;
			}
			
			
			decode[checkByte] = passBit;
		}
		
		
		for (i = 9; i >= 0; i--){
	        bit = decode[QGlobals.passwordTable[i]];
	        decode[i] = decode[i] ^ bit;
	    }

	    // verify decoded password
		
	    xbit1 = QUtils.makeBE16(decode[0], decode[1]);
	    xbit2 = QUtils.makeBE16(decode[2], decode[3]);
	    xbit3 = QUtils.makeBE16(decode[4], decode[5]);
		
	    x = ~((xbit1 + xbit2) + xbit3) & 0xffff;
	    y = QUtils.makeBE16(decode[6], decode[7]);
	    
	    if(y != x){	    	
	    	throw new FailedDecode("Password does not hash!!");
	    }
	    
	    x = ~(xbit1 ^ (xbit2 ^ xbit3)) & 0xffff;
	    y = QUtils.makeBE16(decode[8], decode[9]);
	    
	    if(y != x){
	    	throw new FailedDecode("Password does not hash!");
	    }
	    
	    // verify map
	    if ((decode[0] >> 2) >= 33){
	    	throw new FailedDecode("Invalid destination level!");
	    }
	    
	    // verify skill
	    if ((decode[0] & 3) > 4){	        
	        throw new FailedDecode("Invalid skill level!");
	    }
	    
	    // verify ammo
	    if ((decode[2] & 0x0f) >= 9 || (decode[2] >> 4) >= 9 || (decode[3] & 0x0f) >= 9 || (decode[3] >> 4) >= 9){
	        throw new FailedDecode("Invalid ammo amounts!");
	    }
	    
	    // verify health/armor
	    if ((decode[4] & 0x0f) >= 9 || (decode[4] >> 4) >= 9) {
	    	throw new FailedDecode("Invalid health or armor amount!");
	    }
	    
	    // verify armor type
	    if ((decode[5] & 3) >= 3){
	        throw new FailedDecode("Invalid armor type!");
	    }
	    
	    bit = 0;
	    
	    fields.setNextMap(decode[0] >> 2);
	    fields.setGameSkill(decode[0] & 3);
	    
	    for(i =0;i< QGlobals.numWeapons;i++){
	    	if(i != 1 && i != 2){
	    		int temp = decode[1] & (1 << bit);
	    		if(temp != 0){
	    			fields.setChecked(QGlobals.weaponControlForIndex[i], true);
	    		}else{
	    			fields.setChecked(QGlobals.weaponControlForIndex[i], false);
	    		}
	    		++bit;
	    	}
	    }
	    
	    int[] ammoToUse;
	    
	    if((decode[5] & 0x80) != 0){
	    	fields.setBackpack(true);
	    	ammoToUse = QGlobals.backMaxAmmo;
	    }else{
	    	fields.setBackpack(false);
	    	ammoToUse = QGlobals.normMaxAmmo;
	    }	    
	    
	    
	    // get ammo amounts
	    fields.setClip(decodePassItem(decode[2] >> 4,ammoToUse[0]));
	    fields.setShells(decodePassItem(decode[2] & 0x0f,ammoToUse[1]));
	    fields.setRockets(decodePassItem(decode[3] & 0x0f,ammoToUse[2]));
	    fields.setCells(decodePassItem(decode[3] >> 4,ammoToUse[3]));
	    
	    
	    // get health and armor
	    fields.setHealth(decodePassItem(decode[4] >> 4, 200));
	    fields.setArmor(decodePassItem(decode[4] & 0x0f, 200));
	    
	    // get armor	    
	    fields.setArmorType(decode[5] & 3);
	    
	    // get artifacts
	    int artifacts = (decode[5] >> 2) & 7;
	    
	    if((artifacts & 1) != 0){
	    	fields.setChecked(QGlobals.ARTIFACT_FAST, true);
	    }
	    if((artifacts & 2) != 0){
	    	fields.setChecked(QGlobals.ARTIFACT_TRIPLE, true);
	    }
	    if((artifacts & 4) != 0){
	    	fields.setChecked(QGlobals.ARTIFACT_DOUBLE, true);
	    }
	    
	    return fields;
	}
	
	
	private static int decodePassItem(int bytecode,int maxvalue){
		int value;
		int bitsum = bytecode * maxvalue;
		
		if(bitsum >= 0){
			value = bitsum >> 3;
		}else{
			value = (bitsum + 7) >> 3;
		}		
		
		return value;
	}
}
